use futures_util::SinkExt;
use futures_util::stream::StreamExt;
use serde_json::Value;
use std::time::Duration;
use tokio::sync::mpsc;
use tokio_tungstenite::connect_async;

pub enum GdCommand {
    SendMessage(Value),
    Reconnect,
    Shutdown,
}

pub struct GdManager {
    ws_url: String,
    command_tx: mpsc::Sender<GdCommand>,
}

impl GdManager {
    /// Create a new godot manager
    pub fn new(ws_url: &str) -> anyhow::Result<Self> {
        let (command_tx, command_rx) = mpsc::channel(32);

        tokio::spawn(Self::run_websocket_client(ws_url.to_string(), command_rx));

        Ok(Self {
            ws_url: ws_url.to_string(),
            command_tx,
        })
    }

    /// Create a new websocket client
    async fn run_websocket_client(ws_url: String, mut command_rx: mpsc::Receiver<GdCommand>) {
        let mut retry_delay = Duration::from_secs(1);

        loop {
            match connect_async(&ws_url).await {
                Ok((ws_stream, _)) => {
                    retry_delay = Duration::from_secs(1);
                    let (mut write, mut read) = ws_stream.split();

                    if let Err(e) = write.send("ping".into()).await {
                        tracing::error!("Initial ping failed: {}", e);
                        continue;
                    }

                    let mut connection_alive = true;

                    while connection_alive {
                        tokio::select! {
                            Some(cmd) = command_rx.recv() => {
                                match cmd {
                                    GdCommand::SendMessage(msg) => {
                                        if let Err(e) = write.send(msg.to_string().into()).await {
                                            tracing::error!("Send failed: {}", e);
                                            connection_alive = false;
                                        }
                                    },
                                    GdCommand::Reconnect => {
                                        tracing::error!("Reconnect server");
                                        connection_alive = false;
                                    },
                                    GdCommand::Shutdown => return
                                }
                            },
                            msg = read.next() => match msg {
                                Some(Ok(_)) => {},
                                Some(Err(e)) => {
                                    tracing::warn!("Connection error: {}", e);
                                    connection_alive = false;
                                },
                                None => {
                                    tracing::warn!("Connection closed by server");
                                    connection_alive = false;
                                }
                            }
                        }
                    }
                }
                Err(e) => {
                    tracing::warn!("Connection failed, Retrying in {:?}... {}", retry_delay, e);
                    tokio::time::sleep(retry_delay).await;
                    retry_delay = (retry_delay * 2).min(Duration::from_secs(30));
                }
            }
        }
    }

    /// Send message by websocket client
    pub async fn send(&self, msg: Value) -> Result<(), String> {
        self.command_tx
            .send(GdCommand::SendMessage(msg))
            .await
            .map_err(|e| e.to_string())
    }

    /// Reconnect to websocket server
    pub async fn reconnect(&self) -> Result<(), String> {
        self.command_tx
            .send(GdCommand::Reconnect)
            .await
            .map_err(|e| e.to_string())
    }

    /// Shutdown the websocket client
    pub async fn shutdown(&self) -> Result<(), String> {
        tracing::error!("Shutdown from {}", self.ws_url);
        self.command_tx
            .send(GdCommand::Shutdown)
            .await
            .map_err(|e| e.to_string())
    }
}
